package de.hb.bv2.u04;

import java.util.ArrayList;

import de.hb.bv2.u04.ContourSet;
import de.hb.bv2.u04.ContourTracer;

import ij.process.ImageProcessor;
import ij.gui.Line;


/**
 * In dieser Klasse sind alle Methoden zur vierten
 * Uebung enthalten.
 */
public final class Gruppe03_U04_Logic 
{		
    /**
     * Diese Methode berechnet die konvexen Huellen, fuer die sich
     * in dem Bild befindenden Objekten. Die konvexen Huellen werden
     * in der Farbe Grau um das Objekt herum eingezeichnet.
     * 
     * @param ip Ursprungsbild
     */
	public static void KonvexeHuelle(final ImageProcessor ip) 
	{		
		// Die Konturen extrahieren.
		ContourTracer tracer = new ContourTracer(ip);
		ContourSet cs = tracer.getContours();
		
		// In dieser Schleife werden fuer die auesseren Konturen
		// die konvexen Huellen berechnet und eingezeichnet.
		for(int i = 0; i < cs.outerContours.size(); i++) 
		{
			// Konvexe Huelle bestimmen.
			QuickHull temp = KonvexeHuelle_Funktion(cs.outerContours.get(i));
			
		    // Die konvexe Huelle einzeichnen.
		    KonvexeHuelleZeichnen(ip, temp.p, temp.h);
		}
		
		// In dieser Schleife werden fuer die inneren Konturen
		// die konvexen Huellen berechnet und eingezeichnet.
		for(int i = 0; i < cs.innerContours.size(); i++) 
		{
			// Konvexe Huelle bestimmen.
			QuickHull temp = KonvexeHuelle_Funktion(cs.innerContours.get(i));
			
		    // Die konvexe Huelle einzeichnen.
		    KonvexeHuelleZeichnen(ip, temp.p, temp.h);
		}
	}
	
	
    /**
     * Diese Methode berechnet fuer die uebergebende Kontur die 
     * konvexe Huelle.
     * 
     * @param contour Die Kontur, fuer die die konvexe Huelle
     *                berechnet werden soll.
     */
	public static QuickHull KonvexeHuelle_Funktion(Contour contour) 
	{	
		// Fuer die Konturpunkte wird ein Array vom Typ Point instanziiert.
		Point[] p = new Point[contour.nodes.size()];
		
		// In dieser Schleife werden die Knoten der Kontur in Punkte
		// vom Typ Point umgewandelt.
		for(int i = 0; i < contour.nodes.size(); i++)
		{
			Node temp = contour.nodes.get(i);
			
			p[i] = new Point(temp.x, temp.y);
		}
		
		// Ein Objekt der Klasse QuickHull instanziieren um die
		// konvexe Hulle nach dem Quick Hull Algorithmus zu berechnen.
		QuickHull c = new QuickHull();
		
		// Durch diesen Methodenaufruf wird die konvexe Huelle berechnet.
		// Die ersten h Punkte von p bilden dabei die konvexe Huelle.
	    c.h = c.computeHull(p);
	    
	    return c;
	}
	
	
    /**
     * Konvexe Huelle zeichnen.
     * 
     * @param ip Ursprungsbild
     * @param p Konturpunkte
     * @param h Die ersten h Konturpunkte von p bilden die 
     *          konvexe Huelle.
     */
	public static void KonvexeHuelleZeichnen(final ImageProcessor ip, Point[] p, int h) 
	{		
		// Die Farbe fuer die konvexe Huelle auf Grau setzen.
		ip.setColor(127);
		
		// Die konvexe Huelle einzeichnen.
		for(int i = 0; i < h; i++) 
		{
			Line line = new Line(
					p[i].x,
					p[i].y,
					p[(i+1)%h].x,
					p[(i+1)%h].y);
			line.drawPixels(ip);
		}
	}

	
    /**
     * Diese Methode extrahiert aus einem Bild alle Konkavitaeten.
     * Die Konkavitaeten werden in der Farbe Grau in das Bild
     * eingezeichnet. Die Konturen der Objekte werden in Schwarz
     * eingezeichnet.
     * 
     * @param ip Ursprungsbild
     */
	public static void RCC_TopologischeRelationen(final ImageProcessor ip) 
	{
		// Die Konturen extrahieren.
		ContourTracer tracer = new ContourTracer(ip);
		ContourSet cs = tracer.getContours();
		
		// Das Ausgabebild komplett weiss faerben.
		for(int x = 0; x < ip.getWidth(); x++)
		{
			for(int y = 0; y < ip.getHeight(); y++)
			{
				ip.putPixel(x, y, 255);
			}
		}
		
		// Hier werden alle Konturen in Schwarz eingezeichnet.
		Konturzeichnen(ip, cs);
		
		// In dieser Schleife werden fuer die auesseren Konturen
		// die Konkavitaeten berechnet und eingezeichnet.
		// Die Ausgabe der topologischen Relationen erfolgt fuer
		// jede Kontur getrennt auf der Konsole.
		for(int i = 0; i < cs.outerContours.size(); i++) 
		{			
			RCC_TopologischeRelationen_Funktion(ip, cs.outerContours.get(i));
		}
		
		// In dieser Schleife werden fuer die inneren Konturen
		// die Konkavitaeten berechnet und eingezeichnet.
		// Die Ausgabe der topologischen Relationen erfolgt fuer
		// jede Kontur getrennt auf der Konsole.
		for(int i = 0; i < cs.innerContours.size(); i++) 
		{
			RCC_TopologischeRelationen_Funktion(ip, cs.innerContours.get(i));
		}		
	}
	
	
    /**
     * Diese Methode berechnet fuer eine Kontur die Konkavitaeten und zeichnet
     * diese in Grau in das Bild ein. Auch werden hier die topologischen Relationen
     * fuer eine Kontur bestimmt und auf der Konsole ausgegeben.
     * 
     * @param ip Ursprungsbild
     * @param contour Die Kontur, fuer die die Konkavitaet und die topologischen
     * 				  Realtionen berechnet werden sollen.
     */
	public static void RCC_TopologischeRelationen_Funktion(final ImageProcessor ip, 
			Contour contour) 
	{
		// Dieser ImageProcessor wird zur Extrahierung der konvexen Huelle benoetigt.
		ImageProcessor copy =  ip.duplicate();
		
		// Konvexe Huelle bestimmen.
		QuickHull c = KonvexeHuelle_Funktion(contour);
		
		// Bild komplett weiss faerben.
		for(int x = 0; x < copy.getWidth(); x++)
		{
			for(int y = 0; y < copy.getHeight(); y++)
			{
				copy.putPixel(x, y, 255);
			}
		}
		
		// Jetzt wird die konvexe Huelle in das weisse Bild
		// eingezeichnet.
	    KonvexeHuelleZeichnen(copy, c.p, c.h);
	    
	    // Nun kann die komplette Kontur der konvexen Huelle 
	    // extrahiert werden.
		ContourTracer tracer = new ContourTracer(copy);
		ContourSet konvex = tracer.getContours();
		
		// Da eine Kontur nur eine konvexe Huelle besitzen kann,
		// wird ab nun nur noch mit einer Liste von konvexen 
		// Knoten gearbeitet.
		ArrayList<Node> konvexNodes = konvex.outerContours.get(0).nodes;
		
		// In diesem ContourSet werden die konkaven Konturpunkte als 
		// aussere Konturen gespeichert.
		ContourSet konkav = new ContourSet(1);
			
		// In dieser Liste werden die Indizes der bereits betrachteten 
		// Konturpunkte gespcihert, damit diese nicht zweimal behandelt
		// werden.
		ArrayList<Integer> untersuchteKnoten = new ArrayList<Integer>();
		
		// Ein Indexzaehler fuer die konkaven Konturen.
		int countKonkav = 0;
		
		// In dieser Schleife werden die konkaven Konturen aus der
		// der Methode uebergebenen Kontur extrahiert.
		for(int i = 0; i < contour.nodes.size(); i++)
		{
			// Befindet sich der aktuelle Konturpunkt nicht in der Liste
			// mit den konvexen Konturpunkte, so handelt es sich um einen 
			// konkaven Konturpunkt. Handelt es sich um einen konkaven 
			// Konturpunkt und wurde dieser Punkt noch nicht betrachtet,
			// so kann von diesem Konturpunkt aus eine konkave Kontur
			// extrahiert werden.
			if(!isInside(konvexNodes, contour.nodes.get(i))
					&& !untersuchteKnoten.contains(i))
			{
				// Speicher fuer eine neue konkave Kontur in Form einer
				// ausseren Kontur anfordern.
				konkav.outerContours.add(new OuterContour(countKonkav, 1));
				
				// Den aktuellen Knoten als betrachtet speichern und als
				// konkaven Konturpunkt speichern.
				untersuchteKnoten.add(i);
				konkav.outerContours.get(countKonkav).nodes.add(contour.nodes.get(i));
				
				// Jetzt werden die Konturpunkte links und rechts vom
				// gefundenen konkaven Konturpunkt aus untersucht.
				
				// Flags, die angeben, ob links oder rechts vom aktuellem Konturpunkt
				// aus, das Ende der konkaven Kontur, bzw. ein konvexer Konturpunkt 
				// gefunden wurde.
				boolean endeLinks = false;
				boolean endeRechts = false;
				
				// Indizes fuer die Suche links und rechts vom aktuellem Konturpunkt.
				int i_Links = i;
				int i_Rechts = i;
				
				// In dieser Schleife werden die Konturpunkt links vom aktuellem Konturpunkt
				// aus untersucht. Es werden dabei solange Konturpunkte zur
				// konkaven Kontur hinzugefuegt, bis ein konvexer Konturpunkt erreicht wird. 
				while(!endeLinks)
				{
					// Indexumrechnung, falls der Anfang der Liste erreicht wird.
					if(i_Links == 0)
					{
						i_Links = contour.nodes.size()-1;
					} else {
						i_Links--;
					}
					
					// Ist der Konturpunkt nicht in der Liste mit den konvexen Konturpunkte,
					// so handelt es sich um einen konkaven Konturpunkt. Dieser wird in die 
					// Liste der konkaven Konturpunkte gespeichert. Sobald ein konvexer
					// Konturpunkt erreicht wird, wird dieser noch hinzugefuegt und der
					// Flag auf true gesetzt, sodass die Schleife nun endet.
					if(!isInside(konvexNodes, contour.nodes.get(i_Links)))
					{
						konkav.outerContours.get(countKonkav).nodes.add(0, contour.nodes.get(i_Links));
					} else {
						konkav.outerContours.get(countKonkav).nodes.add(0, contour.nodes.get(i_Links));
						endeLinks = true;
					}
					
					// Den aktuellen Knoten als betrachtet speichern.
					untersuchteKnoten.add(i_Links);
				}
				
				// In dieser Schleife werden die Konturpunkt rechts vom aktuellem Konturpunkt
				// aus untersucht. Es werden dabei solange Konturpunkte zur
				// konkaven Kontur hinzugefuegt, bis ein konvexer Konturpunkt erreicht wird. 
				while(!endeRechts)
				{
					// Indexumrechnung, falls das Ende der Liste erreicht wird.
					if(i_Rechts == (contour.nodes.size()-1))
					{
						i_Rechts = 0;
					} else {
						i_Rechts++;
					}
					
					// Ist der Konturpunkt nicht in der Liste mit den konvexen Konturpunkte,
					// so handelt es sich um einen konkaven Konturpunkt. Dieser wird in die 
					// Liste der konkaven Konturpunkte gespeichert. Sobald ein konvexer
					// Konturpunkt erreicht wird, wird dieser noch hinzugefuegt und der
					// Flag auf true gesetzt, sodass die Schleife nun endet.
					if(!isInside(konvexNodes, contour.nodes.get(i_Rechts)))
					{
						konkav.outerContours.get(countKonkav).nodes.add(contour.nodes.get(i_Rechts));
					} else {
						konkav.outerContours.get(countKonkav).nodes.add(contour.nodes.get(i_Rechts));
						endeRechts = true;
					}
					
					// Den aktuellen Knoten als betrachtet speichern.
					untersuchteKnoten.add(i_Rechts);
				}

				// Indexzaehler fuer die konkaven Konturen erhoehen.
				countKonkav++;
			}
		}
			
		// Hier wird die Intensitaet des Bildes auf 127 (Grau) gesetzt. Und die
		// konkaven Konturen in grau eingezeichnet.
		ip.setColor(127);
		Konturzeichnen(ip, konkav);
		
		// Anzahl an Konkavitaeten ausgeben.
		System.out.println("\nAnzahl an Konkavitaeten: " + konkav.outerContours.size());
		
		// Nun werden die topologischen Lagen der Konkavitateten bestimmt.
		
		String EC = "";	// String fuer die Konkavitaeten, die extern verbunden sind.
		String DC = ""; // String fuer die Konkavitaeten, die nicht verbunden sind.
				
		// Innerhalb dieser beiden Schleifen werden alle Kombinationen der
		// Konkavitaeten auf ihre topologische Lage zueinander untersucht.
		for(int i = 0; i < konkav.outerContours.size()-1; i++)
		{		
			// Liste mit Konturpunkten einer konkaven Kontur.
			ArrayList<Node> nodes1 = konkav.outerContours.get(i).nodes;
			
			// In dieser Schleife werden alle Kombinationen der Kontur
			// aus der ersten Schleife mit den anderen Konturen betrachtet.
			for(int j = i+1; j < konkav.outerContours.size(); j++)
			{
				// Eine weitere Liste mit Konturpunkten einer konkaven Kontur.
				ArrayList<Node> nodes2 = konkav.outerContours.get(j).nodes;
				
				// Sind beide Konturen miteinander verbunden, so werden die 
				// EC und DC Strings um die Kombination erweitert.
				if(isConected(nodes1, nodes2))
				{
					EC += "EC(K" + i + ",K" + j + ") ^ ";
					DC += "(" + i + "," + j + "),";
				}
			}
		}
		
		// Ist der DC String nicht leer, so wird das letzte Zeichen, ein Komma 
		// entfernt, da dieses Zeichen zuviel ist.
		if(!DC.isEmpty()) DC = DC.substring(0, DC.length()-1);
		
		// Nun kann fuer alle Paare von Konkavitaeten ihre topologische Relation
		// angegeben werden.
		System.out.println("\nTopologische Lage:");
		System.out.println(EC + "fuer alle i,j Element von y: DC(Ki, Kj), " +
				"mit y = {(i,j): i < j} - {" + DC + "}\n");
	}
	
	
	/**
     * Diese Methode gibt an, ob zwei Konturen miteinander verbunden sind.
     * 
     * @param nodes1 Die erste Liste mit Knoten, die untersucht werden
     *               soll.
     * @param nodes2 Die zweite Liste mit Knoten, die untersucht werden
     *               soll.
     *          
     * @return True, wenn beide Konturen verbunden sind
     *         Ansonsten False.
     */
	public static boolean isConected(ArrayList<Node> nodes1, ArrayList<Node> nodes2) 
	{
		// Flag, der angibt, ob zwei Konturen verbunden sind.
		boolean isConected = false;
		
		// Hier wird untersucht, ob zwei Konturen verbunden sind.
		for(int i = 0; i < nodes1.size(); i++)
		{
			Node temp1 = nodes1.get(i);
			
			for(int j = 0; j < nodes2.size(); j++)
			{
				Node temp2 = nodes2.get(j);
				
				// Sind die x- und die y-Koordinaten
				// identisch, so sind beide Konturen
				// verbunden.
				if(temp1.x == temp2.x && temp1.y == temp2.y)
				{
					isConected = true;
					break;
				}
			}

		}
		
		return isConected;			
	}
	
	
    /**
     * Diese Methode gibt an, ob ein Knoten innerhalb einer
     * Liste mit Knoten ist.
     * 
     * @param nodes Die Liste mit Knoten, die untersucht werden
     *              soll.
     * @param n Der Knoten, der in der Liste mit Knoten gesucht
     *          werden soll.
     *          
     * @return True, wenn sich n in der Liste befindet.
     *         Ansonsten False.
     */
	public static boolean isInside(ArrayList<Node> nodes, Node n) 
	{
		// Flag, der angibt, ob der Knoten in der Liste ist.
		boolean isInside = false;
		
		// Hier wird der Knoten gesucht.
		for(int i = 0; i < nodes.size(); i++)
		{
			Node temp = nodes.get(i);
			
			// Sind die x- und die y-Koordinaten
			// identisch, so befindet sich der Knoten
			// in der Liste.
			if(temp.x == n.x && temp.y == n.y)
			{
				isInside = true;
				break;
			}
		}
		
		return isInside;			
	}


    /**
     * Kontur zeichnen.
     * 
     * @param ip Ursprungsbild
     * @param cs Konturen, aufgeteilt in innere und aeussere Konturen.
     */
	public static void Konturzeichnen(final ImageProcessor ip, ContourSet cs) 
	{		
		// Aeussere Konturen einzeichnen
		for(int i = 0; i < cs.outerContours.size(); i++) 
		{
			for(int j = 0; j < cs.outerContours.get(i).nodes.size(); j++) 
			{
				Line aussen = new Line(
						cs.outerContours.get(i).nodes.get(j).x,
						cs.outerContours.get(i).nodes.get(j).y,
						cs.outerContours.get(i).nodes.get((j+1)%cs.outerContours.get(i).nodes.size()).x,
						cs.outerContours.get(i).nodes.get((j+1)%cs.outerContours.get(i).nodes.size()).y);
				aussen.drawPixels(ip);
			}
		}
			
		// Innere Konturen einzeichnen
		for(int i = 0; i < cs.innerContours.size(); i++) 
		{
			for(int j = 0; j < cs.innerContours.get(i).nodes.size(); j++) 
			{
				Line innen = new Line(
						cs.innerContours.get(i).nodes.get(j).x,
						cs.innerContours.get(i).nodes.get(j).y,
						cs.innerContours.get(i).nodes.get((j+1)%cs.innerContours.get(i).nodes.size()).x,
						cs.innerContours.get(i).nodes.get((j+1)%cs.innerContours.get(i).nodes.size()).y);
				innen.drawPixels(ip);
			}
		}	
	}
}
